package com.github.jcooky.structure;

public class LinkedList implements NodeList {
	private LinkedNode header = new LinkedNode(), trailer = new LinkedNode();
	private int n = 0;
	
	private class LinkedNode extends NodeList.Node {
		private Object e = null;
		private LinkedNode prev = null, next = null;
		
		public Object element() {
			return e;
		}

		public void setElement(Object e) {
			this.e = e;
		}

		public void setPrev(com.github.jcooky.structure.NodeList.Node p) {
			prev = (LinkedNode)p;
		}

		public void setNext(com.github.jcooky.structure.NodeList.Node p) {
			next = (LinkedNode)p;
		}

		public com.github.jcooky.structure.NodeList.Node getPrev() {
			return prev;
		}

		public com.github.jcooky.structure.NodeList.Node getNext() {
			return next;
		}

		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + ((e == null) ? 0 : e.hashCode());
			result = prime * result + ((next == null) ? 0 : next.hashCode());
			result = prime * result + ((prev == null) ? 0 : prev.hashCode());
			return result;
		}

		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			LinkedNode other = (LinkedNode) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (e == null) {
				if (other.e != null)
					return false;
			} else if (!e.equals(other.e))
				return false;
			if (next == null) {
				if (other.next != null)
					return false;
			} else if (!next.equals(other.next))
				return false;
			if (prev == null) {
				if (other.prev != null)
					return false;
			} else if (!prev.equals(other.prev))
				return false;
			return true;
		}

		private LinkedList getOuterType() {
			return LinkedList.this;
		}

		public String toString() {
			return "LinkedNode [e=" + e + ", prev=" + prev + ", next=" + next + "]";
		}
		
	}

	public int size() {
		return n;
	}

	public boolean isEmpty() {
		return n == 0;
	}

	public Node first() {
		return header.getNext();
	}

	public Node last() {
		return trailer.getPrev();
	}

	public Node prev(Node p) {
		return p.getPrev();
	}

	public Node next(Node p) {
		return p.getNext();
	}

	public void set(Node p, Object e) {
		p.setElement(e);
	}

	public void addBefore(Node p, Object e) {
		Node q = new LinkedNode();
		q.setElement(e);
		
		q.setNext(p);
		p.getPrev().setNext(q);
		q.setPrev(p.getPrev());
		p.setPrev(q);
	}

	public void addAfter(Node p, Object e) {
		Node q = new LinkedNode();
		q.setElement(e);
		
		q.setPrev(p);
		q.setNext(p.getNext());
		p.getNext().setPrev(p);
		p.setNext(q);
	}

	public void addFirst(Object e) {
		addBefore(first(), e);
	}

	public void addLast(Object e) {
		addAfter(last(), e);
	}

	public Object remove(Node p) {
		Object t = p.element();
		
		p.getPrev().setNext(p.getNext());
		p.getNext().setPrev(p.getPrev());
		
		p.setPrev(null);
		p.setNext(null);
		
		return t;
	}

	public Iterator iterator() {
		return new Iterator() {
			private Node p;
			
			public boolean hasNext() {
				return p != last().getNext();
			}

			public Object next() {
				Object e = p.element();
				p = p.getNext();
				
				return e;
			}
			
		};
	}

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((header == null) ? 0 : header.hashCode());
		result = prime * result + n;
		result = prime * result + ((trailer == null) ? 0 : trailer.hashCode());
		return result;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		LinkedList other = (LinkedList) obj;
		if (header == null) {
			if (other.header != null)
				return false;
		} else if (!header.equals(other.header))
			return false;
		if (n != other.n)
			return false;
		if (trailer == null) {
			if (other.trailer != null)
				return false;
		} else if (!trailer.equals(other.trailer))
			return false;
		return true;
	}
	
}
